package cn.wecloud.im.multiplayervideodemo.live

import android.app.Application
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import cn.wecloud.im.core.im.*
import cn.wecloud.im.core.im.callback.ICallback
import cn.wecloud.im.core.im.messages.MemberDto
import cn.wecloud.im.core.im.messages.MultiMeetEvent
import cn.wecloud.im.exception.WeException
import cn.wecloud.im.multiplayervideodemo.Constant
import cn.wecloud.im.multiplayervideodemo.utils.AppSharePre
import com.github.ajalt.timberkt.Timber
import io.livekit.android.LiveKit
import io.livekit.android.RoomOptions
import io.livekit.android.VideoMember
import io.livekit.android.events.RoomEvent
import io.livekit.android.events.collect
import io.livekit.android.room.Room
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.track.*
import io.livekit.android.util.flow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import livekit.LivekitModels
import livekit.LivekitRtc
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import kotlin.collections.set

@OptIn(ExperimentalCoroutinesApi::class)
class MultipleVideoViewModel(
    val url: String,
    val args: MultiVideoArgs,
    application: Application
) : AndroidViewModel(application) {
    enum class VideoStatus {
        prepare,
        start,
        end,
    }

    private var timer: Timer = Timer()

    private val waitingMemberCache: ConcurrentMap<String, TimerTask> = ConcurrentHashMap()

    //视频状态是否开启
    val videoStatus = MutableStateFlow(VideoStatus.prepare)
    private val mutableRoom = MutableStateFlow<Room?>(null)

    private var mutableRemoteVideoMembers = MutableStateFlow<Map<String, VideoMember>>(emptyMap())
    val videoMembers = mutableRemoteVideoMembers
        .combine(mutableRoom) { videoMembersMap, room ->
            if (room != null) {
                listOf(localVideoMember) + videoMembersMap.keys
                    .sortedBy { it }
                    .mapNotNull { videoMembersMap[it] }
            } else {
                emptyList()
            }
        }


    val room: MutableStateFlow<Room?> = mutableRoom
    private val remoteParticipants = mutableRoom.flatMapLatest { room ->
        if (room != null) {
            room::remoteParticipants.flow
                .map { remoteParticipants ->
                    remoteParticipants
                        .keys
                        .sortedBy { it }
                        .map { remoteParticipants[it]?.identity!! to remoteParticipants[it]!! }
                        .toMap()
                }
        } else {
            flowOf(emptyMap())
        }
    }

    private val mutableError = MutableStateFlow<Throwable?>(null)
    val error = mutableError.hide()

    //主镜头参与者
    private val mutableMainVideoMember = MutableStateFlow<VideoMember?>(null)
    val mainVideoMember: StateFlow<VideoMember?> = mutableMainVideoMember

    //次镜头参与者
    private val mutableSecondVideoMember = MutableStateFlow<VideoMember?>(null)
    val secondVideoMember: StateFlow<VideoMember?> = mutableSecondVideoMember

    private var localVideoMember: VideoMember? = null

    //活跃的发言人
    val activeSpeakers = mutableRoom.flatMapLatest { room ->
        if (room != null) {
            room::activeSpeakers.flow
        } else {
            flowOf(emptyList())
        }
    }

    //麦克风
    private val mutableMicEnabled = MutableLiveData(true)
    val micEnabled = mutableMicEnabled.hide()

    //摄像头
    private val mutableCameraEnabled = MutableLiveData(true)
    val cameraEnabled = mutableCameraEnabled.hide()

    private val mutablePermissionAllowed = MutableStateFlow(true)
    val permissionAllowed = mutablePermissionAllowed.hide()

    private val TAG = "MultipleVideoViewModel"

    private val multipleVideoReceiveCommand = object : IMMultiMeetHandler() {

        override fun processMultiMeetCallEvent(
            conversation: IMConversation?,
            data: MultiMeetEvent?
        ) {
            //中途有成员发起邀请
            val inviteeUsers =
                data?.memberDtoList?.mapNotNull { if (it.state == MemberDto.STATE_CALLING) it.clientId else null }
                    ?: return
            addInviteMembers(inviteeUsers)
        }

        /**
         * 有Client拒绝加入频道
         */
        override fun processMultiMeetRejectEvent(
            conversation: IMConversation?,
            data: MultiMeetEvent?
        ) {
            val rejectCid = data?.clientId ?: return
            handleMembersLeave(rejectCid)
            Toast.makeText(
                application, "${rejectCid}拒绝你的通话",
                Toast.LENGTH_LONG
            ).show()
        }

        /**
         * 有Client同意加入频道
         */
        override fun processMultiMeetAgreeEvent(
            conversation: IMConversation?,
            data: MultiMeetEvent?
        ) {
            val clientId = data?.clientId ?: return
            val timeoutTask = waitingMemberCache.get(clientId)
            waitingMemberCache.remove(clientId)
            val res = timeoutTask?.cancel()
        }

        /**
         * 有Client离开频道
         */
        override fun processMultiMeetLeaveEvent(
            conversation: IMConversation?,
            data: MultiMeetEvent?
        ) {
            val leaveCid = data?.clientId ?: return
            handleMembersLeave(leaveCid)
            Toast.makeText(
                application, "${leaveCid}退出通话。",
                Toast.LENGTH_LONG
            ).show()
        }

        /**
         * Client占线
         */
        override fun processMultiMeetBusyEvent(
            conversation: IMConversation?,
            data: MultiMeetEvent?
        ) {
            val busyCid = data?.busyClientId ?: return
            handleMembersLeave(busyCid)
            Toast.makeText(
                application, "${busyCid}占线中，请稍后在试。",
                Toast.LENGTH_LONG
            ).show()
        }
    }

    init {
        val timerTask = object : TimerTask() {
            override fun run() {
                IMClient.getInstance().multiMeetHeartbeat(0L, args.roomId, null)
            }
        }
        timer.schedule(timerTask, 0, 5000)
        IMMessageManager.registerMultiMeetMessageHandler(multipleVideoReceiveCommand)
    }

    fun initRtc() {
        viewModelScope.launch {

            launch {
                error.collect { Timber.e(it) }
            }

            try {
                val room = LiveKit.connect(
                    getApplication(),
                    url,
                    args.token,
                    roomOptions = RoomOptions(adaptiveStream = true, dynacast = true),
                )

                // Create and publish audio/video tracks
                val localParticipant = room.localParticipant
                localParticipant.setMicrophoneEnabled(true)
                mutableMicEnabled.postValue(localParticipant.isMicrophoneEnabled())

                localParticipant.setCameraEnabled(true)
                mutableCameraEnabled.postValue(localParticipant.isCameraEnabled())
                mutableRoom.value = room

                localVideoMember = VideoMember(
                    AppSharePre.getClientId(), MemberDto.STATE_ANSWERED, localParticipant
                )
                mutableSecondVideoMember.value = localVideoMember

                initMembers()

                launch {
                    remoteParticipants.collect {
                        handleMembers(it)
                    }
                }


                launch {
                    room.events.collect {
                        when (it) {
                            is RoomEvent.FailedToConnect -> mutableError.value = it.error
                            is RoomEvent.TrackSubscribed -> {
                                val publication = it.publication
                                if (publication is RemoteTrackPublication) {
                                    publication.setVideoQuality(LivekitModels.VideoQuality.MEDIUM)
                                }
                            }
                            else -> {
                            }
                        }
                    }
                }

                if (args.clientId == AppSharePre.getClientId()) {
                    //自己是发起人需要发出邀请
                    meetInvite(args.roomId, args.memberDtoList.mapNotNull { it.clientId })
                }
            } catch (e: Throwable) {
                mutableError.value = e
            }
        }
    }

    private fun initMembers() {
        val members = mutableRemoteVideoMembers.value.toMutableMap()
        args.memberDtoList.map {
            if (AppSharePre.getClientId() != it.clientId) {
                members[it.clientId] =
                    VideoMember(it.clientId, it.state, null)
            }
        }
        if (AppSharePre.getClientId() != args.clientId) {
            //添加邀请人
            members[args.clientId] = VideoMember(args.clientId, MemberDto.STATE_ANSWERED, null)
        }
        members.forEach { (cid, member) ->
            if (member.state == MemberDto.STATE_CALLING || member.state == MemberDto.STATE_NOT_ANSWERED) {
                addWaitingMemberCache(cid)
            }
        }
        mutableRemoteVideoMembers.value = members
    }

    private fun handleMembers(remoteParticipants: Map<String, Participant>) {
        val members = mutableRemoteVideoMembers.value.toMutableMap()
        val iterator = members.iterator()
        val joinCids = remoteParticipants.keys.toMutableList()
        //清除加入成员的超时倒计时
        joinCids.forEach { clientId ->
            val timeoutTask = waitingMemberCache[clientId]
            waitingMemberCache.remove(clientId)
            timeoutTask?.cancel()
        }
        while (iterator.hasNext()) {
            val next = iterator.next()
            val member = next.value
            val participant = remoteParticipants[next.key]
            if (participant != null) {  //更新进入用户状态
                member.updateParticipant(participant)
                member.updateStatus(MemberDto.STATE_ANSWERED)
            } else if (member.state == MemberDto.STATE_ANSWERED) {
                //删除离开用户
                iterator.remove()
            }
            joinCids.remove(next.key)
        }
        //防止时间不同步超时成员又加入进来
        if (joinCids.size > 0) {
            joinCids.forEach { clientId ->
                members[clientId] =
                    VideoMember(clientId, MemberDto.STATE_ANSWERED, remoteParticipants[clientId])
            }
        }
        mutableRemoteVideoMembers.value = members
        //处理主视频成员
        handleMainVideoMember(members)

        if (remoteParticipants.isNotEmpty()) {
            videoStatus.value = VideoStatus.start
        } else if (members.isEmpty() || (members.size == 1 && members.contains(AppSharePre.getClientId()))) {
            videoStatus.value = VideoStatus.end
        }
    }

    private fun handleMainVideoMember(remoteVideoMember: Map<String, VideoMember>) {
        var mainParticipant = mutableMainVideoMember.value
        //过滤出已进入的参与者clientId
        val participant =
            remoteVideoMember.values.mapNotNull { if (it.participant != null) it.clientId else null }
        if (participant.isNotEmpty()) {
            //主镜头为空替换一个远程的
            if (mainParticipant == null) {
                mainParticipant = remoteVideoMember[participant.first()]
            }
            //主镜头人已离开
            if (!remoteVideoMember.keys.contains(mainParticipant?.clientId)) {
                mainParticipant = remoteVideoMember[participant.firstOrNull() ?: ""]
            }
        } else {
            mainParticipant = null
        }
        mutableMainVideoMember.value = mainParticipant
    }

    //切换主参与者
    fun changePrimaryParticipant(videoMember: VideoMember) {
        mutableMainVideoMember.value = videoMember
    }

    override fun onCleared() {
        mutableRoom.value?.disconnect()
        timer.cancel()
        IMMessageManager.unregisterMultiMeetMessageHandler(multipleVideoReceiveCommand)
        super.onCleared()
    }


    private fun handleMembersLeave(clientId: String) {
        val members = mutableRemoteVideoMembers.value.toMutableMap()
        val iterator = members.iterator()
        while (iterator.hasNext()) {
            val next = iterator.next()
            val member = next.value
            if (member.clientId == clientId) {
                //删除离开用户
                iterator.remove()
            }
        }
        mutableRemoteVideoMembers.value = members
        //处理主视频成员
        handleMainVideoMember(members)
        if (members.isEmpty() || (members.size == 1 && members.contains(AppSharePre.getClientId()))) {
            videoStatus.value = VideoStatus.end
        }
    }

    fun remoteVideoMembers(): List<VideoMember> {
        return mutableRemoteVideoMembers.value.values.toList()
    }

    fun setMicEnabled(enabled: Boolean) {
        viewModelScope.launch {
            val localParticipant = room.value?.localParticipant ?: return@launch
            localParticipant.setMicrophoneEnabled(enabled)
            mutableMicEnabled.postValue(enabled)
        }
    }

    fun setCameraEnabled(enabled: Boolean) {
        viewModelScope.launch {
            val localParticipant = room.value?.localParticipant ?: return@launch
            localParticipant.setCameraEnabled(enabled)
            mutableCameraEnabled.postValue(enabled)
        }
    }

    fun flipCamera() {
        room.value?.localParticipant?.let { participant ->
            val videoTrack = participant.getTrackPublication(Track.Source.CAMERA)
                ?.track as? LocalVideoTrack
                ?: return@let

            val newOptions = when (videoTrack.options.position) {
                CameraPosition.FRONT -> LocalVideoTrackOptions(position = CameraPosition.BACK)
                CameraPosition.BACK -> LocalVideoTrackOptions(position = CameraPosition.FRONT)
                else -> LocalVideoTrackOptions()
            }

            videoTrack.restartTrack(newOptions)
        }
    }

    fun dismissError() {
        mutableError.value = null
    }

    fun toggleSubscriptionPermissions() {
        mutablePermissionAllowed.value = !mutablePermissionAllowed.value
        room.value?.localParticipant?.setTrackSubscriptionPermissions(mutablePermissionAllowed.value)
    }

    fun reconnect() {
        Timber.e { "Reconnecting." }
        val room = mutableRoom.value ?: return
        mutableRoom.value = null
        mutableMainVideoMember.value = null
        room.disconnect()
        viewModelScope.launch {
            room.connect(url, args.token)
            mutableRoom.value = room
        }
    }

    fun meetInvite(inviteeUsers: List<String>) {
        addInviteMembers(inviteeUsers)
        meetInvite(args.roomId, inviteeUsers)
    }

    //会议邀请
    private fun meetInvite(roomId: String, inviteeUsers: List<String>) {
        IMClient.getInstance()
            .multiMeetInvite(0L, roomId, inviteeUsers,
                object : ICallback<Boolean>() {
                    override fun internalDone0(t: Boolean?, weException: WeException?) {
                        if (weException != null) {
                            mutableError.value = weException
                            return
                        }
                    }
                })
    }


    private fun addInviteMembers(inviteeUsers: List<String>) {
        val members = mutableRemoteVideoMembers.value.toMutableMap()
        inviteeUsers.map {
            if (AppSharePre.getClientId() != it) {
                members[it] =
                    VideoMember(it, MemberDto.STATE_CALLING, null)
                addWaitingMemberCache(it)
            }
        }
        mutableRemoteVideoMembers.value = members
    }

    private fun addWaitingMemberCache(cid: String) {
        val timerTask = object : TimerTask() {
            override fun run() {
                waitingMemberCache.remove(cid)
                handleMembersLeave(cid)
                Handler(Looper.getMainLooper()).post {
                    Toast.makeText(
                        getApplication(), "${cid}呼叫超时",
                        Toast.LENGTH_LONG
                    ).show()
                }
            }
        }
        timer.schedule(timerTask, Constant.CALL_WAITING_TIME * 1000)
        waitingMemberCache[cid] = timerTask
    }

    fun meetHangUp() {
        IMClient.getInstance().multiMeetLeave(0L, args.roomId, object : ICallback<Boolean>() {
            override fun internalDone0(t: Boolean?, weException: WeException?) {
                videoStatus.value = VideoStatus.end
            }
        })
    }

}

private fun <T> LiveData<T>.hide(): LiveData<T> = this

private fun <T> MutableStateFlow<T>.hide(): StateFlow<T> = this
private fun <T> Flow<T>.hide(): Flow<T> = this